-------------Number Stumper------------
A 4am crack                  2016-11-04
---------------------------------------

Name: Number Stumper
Genre: educational
Year: 1984
Author: Dennis Sorensen
Publisher: The Learning Company
Platform: Apple ][+ or later
Media: single-sided 5.25-inch floppy
OS: custom
Previous cracks: none
Similar cracks:
  #840 Jumpman
  #595 Addition Magician
  #476 Microzine 2

                   ~

               Chapter 0
 In Which Various Automated Tools Fail
          In Interesting Ways


COPYA
  immediate disk read error

Locksmith Fast Disk Backup
  unable to read any track

EDD 4 bit copy (no sync, no count)
  read error on T22
  copy displays a graphical title page
    then hangs with the drive motor on

Copy ][+ nibble editor
  T00 -> standard prologues, modified
    epilogues (FF FF FF)
  T01 -> corrupted address fields
    that claim to be track $00
  T02,03 -> not full tracks? looks
    like they have some standard-ish
    sectors, but not 16 per track
    (also corrupted address fields)
  T04-06 -> corrupted address fields
    that claim to be track $00 (again)
  T07-T18 -> uncorrupted address fields
    but still non-standard epilogues
  T19+ unformatted

When I say "corrupted address fields,"
this is what that looks like:

                 --v--

   COPY ][ PLUS BIT COPY PROGRAM 8.4
(C) 1982-9 CENTRAL POINT SOFTWARE, INC.
---------------------------------------

TRACK: 01  START: 21DC  LENGTH: 189D
       ^^

21B8: FF FF FF FF FF FF FF FF   VIEW
21C0: FF FF FF FF FF FF FF FF
21C8: FF FF FF FF FF FF FF FF
21D0: FF FF FF FF FF FF FF FF
21D8: FF FF FF FF FF D5 AA 96  <-21DD
                     ^^^^^^^^
                 address prologue

21E0: AA AA AA AA AA AA AA AA
      ^^^^^ ^^^^^ ^^^^^ ^^^^^
      V000   T00   S00  chksm

21E8: FF FF FF FF FF CF F3 FC
      ^^^^^^^^
  address epilogue

21F0: FF FF D5 AA AD 9B DB B9
            ^^^^^^^^
         data prologue

21F8: B9 DB F2 DE B9 AE B3 BA

---------------------------------------

  A  TO ANALYZE DATA  ESC TO QUIT

  ?  FOR HELP SCREEN  /  CHANGE PARMS

  Q  FOR NEXT TRACK   SPACE TO RE-READ

                 --^--

The disk is lying to me. The address
field claims to be track $00, but it's
really track $01. Bad disk! Stop lying!

Disk Fixer
  ["O" -> "Input/Output Control"]
    set Address Epilogue to "FF FF FF"
    set Data Epilogue to "FF FF FF"
  T00 readable, disk volume is 000 (!)
  T01-T06 unreadable (no option to
    ignore the corrupted address field)
  T07-T18 readable, disk volume is 001
  T19+ unreadable (unformatted)

Copy ][+ sector editor
  ["P" -> "Sector Editor Patcher"]
    set type to "CUSTOM"
    set Address Epilogue to "FF FF"
    set Data Epilogue to "FF FF FF"
  T00 readable
  T07-T18 readable

  ["P" -> "Sector Editor Patcher"]
    set CHECK TRACK to "NO"
  only parts of T02 and T03 readable:
    T02: S03,04,05,06,07,0A,0B,0C,0D,0E
    T03: S01,02,08,09,0F
  T01 readable!
  T04-06 readable!

Why didn't COPYA work?
  modified epilogue bytes on track $00
  (it never even got to the fun part)

Why didn't Locksmith FDB work?
  ditto

Why didn't my EDD copy work?
  I've seen similar disks, where the
  first N tracks have intentionally
  corrupted address fields. (N varies
  from disk to disk.) There's a custom
  loader that loads the data from those
  corrupted tracks, then transfers
  control to a standard RWTS for the
  rest of the program. Somewhere in the
  corrupted tracks, it will load data
  from consecutive half tracks. (These
  are devilishly difficult to copy, and
  I didn't even try.) That's just an
  educated guess; I could be surprised.

Hey, I can actually validate that guess
in the Copy ][+ nibble editor, which
can read half tracks.

                 --v--

   COPY ][ PLUS BIT COPY PROGRAM 8.4
(C) 1982-9 CENTRAL POINT SOFTWARE, INC.
---------------------------------------

TRACK: 02.50  START: 3700  LENGTH: 198E
       ^^^^^

3BD0: FF FF FF FF FF FF FF FF   VIEW
3BD8: FF FF FF FF FF FF FF FF
3BE0: FF FF FF FF FF FF FF FF
3BE8: FF FF C9 FF FF FF FF FF
3BF0: FF FF FF FF FF D5 AA 96  <-3BF5
                     ^^^^^^^^
                  address prologue

3BF8: AA AA AA AA AF AB AF AB
      ^^^^^ ^^^^^ ^^^^^ ^^^^^
      V000   T00   S0B  chksm

3C00: FF FF FF 9F E7 F9 FE FF
      ^^^^^^^^
  address eplogue

3C08: D5 AA AD A7 B4 BD CD ED
      ^^^^^^^^
   data prologue

3C10: ED 9B ED F2 E9 DF B6 AB

---------------------------------------

  A  TO ANALYZE DATA  ESC TO QUIT

  ?  FOR HELP SCREEN  /  CHANGE PARMS

  Q  FOR NEXT TRACK   SPACE TO RE-READ

                 --^--

Jackpot! (Note that it's still claiming
to be track $00, though, just like the
other whole tracks above and below it.)

Next steps:

  1. Trace the boot
  2. ???

                   ~

               Chapter 1
      In Which We Brag About Our
           Humble Beginnings


I have two floppy drives, one in slot 6
and the other in slot 5. My "work disk"
(in slot 5) runs Diversi-DOS 64K, which
is compatible with Apple DOS 3.3 but
relocates most of DOS to the language
card on boot. This frees up most of
main memory (only using a single page
at $BF00..$BFFF), which is useful for
loading large files or examining code
that lives in areas typically reserved
for DOS.

[S6,D1=original disk]
[S5,D1=my work disk]

The floppy drive firmware code at $C600
is responsible for aligning the drive
head and reading sector 0 of track 0
into main memory at $0800. Because the
drive can be connected to any slot, the
firmware code can't assume it's loaded
at $C600. If the floppy drive card were
removed from slot 6 and reinstalled in
slot 5, the firmware code would load at
$C500 instead.

To accommodate this, the firmware does
some fancy stack manipulation to detect
where it is in memory (which is a neat
trick, since the 6502 program counter
is not generally accessible). However,
due to space constraints, the detection
code only cares about the lower 4 bits
of the high byte of its own address.

Stay with me, this is all about to come
together and go boom.

$C600 (or $C500, or anywhere in $Cx00)
is read-only memory. I can't change it,
which means I can't stop it from
transferring control to the boot sector
of the disk once it's in memory. BUT!
The disk firmware code works unmodified
at any address. Any address that ends
with $x600 will boot slot 6, including
$B600, $A600, $9600, &c.

; copy drive firmware to $9600
*9600<C600.C6FFM

; and execute it
*9600G
...reboots slot 6, loads game...

Now then:

]PR#5
...
]CALL -151

*9600<C600.C6FFM

*96F8L

96F8-   4C 01 08    JMP   $0801

That's where the disk controller ROM
code ends and the on-disk code begins.
But $9600 is part of read/write memory.
I can change it at will. So I can
interrupt the boot process after the
drive firmware loads the boot sector
from the disk but before it transfers
control to the disk's bootloader.

; instead of jumping to on-disk code,
; copy boot sector to higher memory so
; it survives a reboot
96F8-   A0 00       LDY   #$00
96FA-   B9 00 08    LDA   $0800,Y
96FD-   99 00 28    STA   $2800,Y
9700-   C8          INY
9701-   D0 F7       BNE   $96FA

; turn off slot 6 drive motor
9703-   AD E8 C0    LDA   $C0E8

; reboot to my work disk in slot 5
9706-   4C 00 C5    JMP   $C500

*BSAVE TRACE,A$9600,L$109
*9600G
...reboots slot 6...
...reboots slot 5...

]BSAVE BOOT0,A$2800,L$100

Now we get to(*) trace the boot process
one sector, one page, one instruction
at a time.

(*) If you replace the words "need to"
    with the words "get to," life
    becomes amazing.

                   ~

               Chapter 2
         Boot Trace and Chill


]CALL -151

; move boot0 back into place
*800<2800.28FFM

*801L

; set reset vector
0801-   8A          TXA
0802-   4A          LSR
0803-   4A          LSR
0804-   4A          LSR
0805-   4A          LSR
0806-   09 C0       ORA   #$C0
0808-   85 3F       STA   $3F
080A-   8D F3 03    STA   $03F3
080D-   49 A5       EOR   #$A5
080F-   8D F4 03    STA   $03F4
0812-   A9 00       LDA   #$00
0814-   8D F2 03    STA   $03F2

; hmm
0817-   A9 04       LDA   #$04
0819-   48          PHA

; machine initialization (memory banks,
; TEXT, IN#0, PR#0, &c.)
081A-   8D 81 C0    STA   $C081
081D-   20 2F FB    JSR   $FB2F
0820-   8D 52 C0    STA   $C052
0823-   20 89 FE    JSR   $FE89
0826-   20 93 FE    JSR   $FE93

; clear hi-res screen 1
0829-   A2 20       LDX   #$20
082B-   A0 00       LDY   #$00
082D-   84 06       STY   $06
082F-   A9 20       LDA   #$20
0831-   85 07       STA   $07
0833-   98          TYA
0834-   91 06       STA   ($06),Y
0836-   C8          INY
0837-   D0 FB       BNE   $0834
0839-   E6 07       INC   $07
083B-   CA          DEX
083C-   D0 F6       BNE   $0834

; switch to hi-res screen 1 (blank)
083E-   8D 57 C0    STA   $C057
0841-   8D 50 C0    STA   $C050
0844-   8D 54 C0    STA   $C054
0847-   8D 52 C0    STA   $C052

; set up ($3E) vector to point to the
; sector read routine in the disk
; controller ROM
084A-   A9 5C       LDA   #$5C
084C-   85 3E       STA   $3E

; the disk controller ROM always exits
; via $0801, so set that to an RTS so
; we can JSR and not have to set up a
; loop
084E-   A9 60       LDA   #$60
0850-   8D 01 08    STA   $0801

; hmm again
0853-   A9 72       LDA   #$72
0855-   48          PHA

OK, we've now pushed $04/$72 on the
stack. That's probably important.

; multi-sector read
; Y = first logical sector ($01)
; X = last logical sector ($0E)
; A = start address high byte ($09)
0856-   A0 00       LDY   #$00
0858-   84 FC       STY   $FC
085A-   C8          INY
085B-   A9 09       LDA   #$09
085D-   A2 0E       LDX   #$0E

; call multi-sector read routine
085F-   20 65 08    JSR   $0865

; another sector read, this time just
; one sector, into $0400 (X is already
; less than Y on entry, so loop will
; exit after one read)
0862-   A9 04       LDA   #$04
0864-   AA          TAX

; falls through to multi-sector read
; entry point (was also called earlier)
0865-   85 27       STA   $27
0867-   E8          INX
0868-   86 49       STX   $49
086A-   84 F9       STY   $F9

; map logical into physical sector and
; store it in zero page where the disk
; controller ROM will look for it
086C-   B9 83 08    LDA   $0883,Y
086F-   85 3D       STA   $3D

; read sector via disk controller ROM
0871-   20 7E 08    JSR   $087E

; loop until done
0874-   A4 F9       LDY   $F9
0876-   C8          INY
0877-   C4 49       CPY   $49
0879-   90 EF       BCC   $086A
087B-   A5 27       LDA   $27
087D-   60          RTS

; subroutine to read a sector via ($3E)
; which points to $Cx5C, which exits
; via $0801, which is now an "RTS"
; (HOW F---ING ELEGANT IS THAT, RIGHT?)
087E-   A6 2B       LDX   $2B
0880-   6C 3E 00    JMP   ($003E)

; physical to logical sector map
0883- .. .. .. 00 03 05 07 09
0888- 0B 0D 0F 02 04 06 08 0A
0890- 0C 0E 01

That's it. Flexible but compact.

It's a weird combination of reads,
though. It loads a bunch of sectors at
$0900, then the last one at $0400 (!).
That's part of the text page, but it's
hidden during boot because we cleared
the entire hi-res graphics page and
showed that instead.

Of course, we manually pushed $04/$72
on the stack earlier, so once we fall
through to the sector read routine,
read the last sector, and hit the RTS
we put at $0801, we will "return" to
$0472 + 1 = $0473.

Let's interrupt the boot before it gets
there.

                   ~

               Chapter 3
 In Which Things Get Brilliantly Weird


*9600<C600.C6FFM

; set up callback by changing the two
; bytes that are pushed to the stack
96F8-   A9 97       LDA   #$97
96FA-   8D 18 08    STA   $0818
96FD-   A9 04       LDA   #$04
96FF-   8D 54 08    STA   $0854

; start the boot
9702-   4C 01 08    JMP   $0801

; callback is here --
; copy $0400 up to higher memory so it
; survives a reboot
9705-   A0 00       LDY   #$00
9707-   B9 00 04    LDA   $0400,Y
970A-   99 00 24    STA   $2400,Y
970D-   C8          INY
970E-   D0 F7       BNE   $9707

; turn off slot 6 drive motor
9710-   AD E8 C0    LDA   $C0E8

; reboot to my work disk
9713-   4C 00 C5    JMP   $C500

*BSAVE TRACE2,A$9600,L$116
*9600G
...reboots slot 6...
...reboots slot 5...

]BSAVE BOOT1,A$2400,L$100
]CALL -151

The entry point was $0473, so let's
start there. I'll have to leave the
code at $2400. Relative branches will
look correct, but absolute addresses
in $04xx will be +$2000.

*2473L

; zp$4A is important later (see below)
2473-   46 4A       LSR   $4A
2475-   20 33 04    JSR   $0433

*2433L

; call the following line (then fall
; through and do it again)
2433-   20 36 04    JSR   $0436

; save A and Y
2436-   48          PHA
2437-   98          TYA
2438-   48          PHA

; low-level disk stuff (see below)
2439-   A5 FC       LDA   $FC
243B-   85 FD       STA   $FD
243D-   E6 FC       INC   $FC
243F-   A5 FC       LDA   $FC
2441-   29 03       AND   #$03
2443-   0A          ASL
2444-   05 2B       ORA   $2B
2446-   A8          TAY
2447-   B9 81 C0    LDA   $C081,Y

; wait loop
244A-   A9 30       LDA   #$30
244C-   20 A8 FC    JSR   $FCA8

; more low-level disk stuff
244F-   A5 FD       LDA   $FD
2451-   29 03       AND   #$03
2453-   0A          ASL
2454-   05 2B       ORA   $2B
2456-   A8          TAY
2457-   B9 80 C0    LDA   $C080,Y

; more waiting
245A-   A9 30       LDA   #$30
245C-   20 A8 FC    JSR   $FCA8

; restore A and Y on the way out
245F-   68          PLA
2460-   A8          TAY
2461-   68          PLA
2462-   60          RTS

This is a very clever and compact way
to advance the drive head to the next
track. Normally DOS 3.3 keeps track of
this and has a (much more complicated)
routine to move the head back and forth
as needed. But this loader only needs
to move it forward, so the entire
process collapses to this:

1. Set up the Y register to be a slot
   number (x16) plus the appropriate
   phase (0-3, depending on which track
   the drive head is on)

2. LDA $C081,Y to turn on the
   appropriate stepper motor

3. Wait exactly the right amount of
   time (as measured in CPU cycles)

4. LDA $C080,Y to turn off the
   appropriate stepper motor

5. Wait the right amount of time again

...which is exactly what this routine
at $0436 is doing. But that only gets
us halfway there -- literally, it only
moves the drive head by half a track.
But! Since $0433 "falls through" to
$0436, it ends up doing this twice. Two
half tracks equal one whole track, so
calling the routine at $0433 will move
the drive head to the next whole track.

So now we're on track 1.

(By the way, this is why it initialized
zero page $FC to $00 at $0858. That's
the "current" track where the drive
head is at boot; it gets updated when
the drive head advances.)

Everything I know about low-level disk
stepping, I learned from this excellent
Usenet post:
macgui.com/usenet/?group=1&id=31160

                   ~

               Chapter 4
         Every Byte Is Sacred,
         Every Byte Is Great,
         If A Byte Gets Wasted,
         Woz Gets Quite Irate


Continuing from $0478...

; multi-sector read, similar to the one
; we did in boot0
; Y = first logical sector (see below)
; X = last logical sector ($08)
; A = start address high byte ($17)
2478-   A9 17       LDA   #$17
247A-   A2 08       LDX   #$08
247C-   20 13 04    JSR   $0413

*2413L

; Y is the first logical sector (= 0 at
; this entry point, but note you could
; just call $0415 instead if you wanted
; to read a partial track starting at
; some other sector)
2413-   A0 00       LDY   #$00

; store A in zero page $27, used by the
; disk controller ROM routine as the
; target page to store sectors read
; from disk
2415-   85 27       STA   $27

; X is the final sector to read
2417-   E8          INX
2418-   86 49       STX   $49

; Y is the current sector to read
; (starting with whatever was passed in
; and incrementing until it equals the
; value passed in the X register)
241A-   84 F9       STY   $F9
241C-   98          TYA

; But wait, there's more! Based on the
; high bit of zero page $4A, Y is
; either a logical sector (the map of
; logical->physical sectors is at
; $0463) or a physical sector
241D-   24 4A       BIT   $4A
241F-   30 03       BMI   $2424
2421-   B9 63 04    LDA   $0463,Y

; store physical sector in $3D (again,
; used by the disk controller ROM)
2424-   85 3D       STA   $3D

; read sector by jumping to ($003E),
; which points to $Cx5C (e.g. $C65C if
; booting from slot 6) and exit via
; $0801, which is an RTS by now, so
; this just continues to the next line
2426-   20 00 04    JSR   $0400

; increment sector index
2429-   A4 F9       LDY   $F9
242B-   C8          INY

; are there more sectors to read?
242C-   C4 49       CPY   $49

; yes, branch back and repeat
242E-   90 EA       BCC   $241A

; no, exit with last page (+1) in A
; (disk controller ROM increments this
; after storing sector data, so on exit
; this will be the first page that was
; NOT filled with data in this loop)
2430-   A5 27       LDA   $27
2432-   60          RTS

Continuing from $047F...

; call the code we loaded from track 0
; earlier ($900..$16FF) and just now
; ($1700..$1FFF)
247F-   20 00 09    JSR   $0900

Based on listening to the original disk
as it boots, I'm guessing this displays
the graphical title screen. Let's
capture it and find out.

                   ~

               Chapter 5
    In Which We Take A Brief Detour


*9600<C600.C6FFM

; set up callback (same as previous
; trace)
96F8-   A9 97       LDA   #$97
96FA-   8D 18 08    STA   $0818
96FD-   A9 04       LDA   #$04
96FF-   8D 54 08    STA   $0854

; start the boot
9702-   4C 01 08    JMP   $0801

; break to monitor instead of calling
; subroutine at $0900
9705-   A9 4C       LDA   #$4C
9707-   8D 7F 04    STA   $047F
970A-   A9 59       LDA   #$59
970C-   8D 80 04    STA   $0480
970F-   A9 FF       LDA   #$FF
9711-   8D 81 04    STA   $0481

; continue the boot
9714-   4C 73 04    JMP   $0473

*BSAVE TRACE3,A$9600,L$117
*9600G
...reboots slot 6...
<beep>

*900G
...displays graphical title screen...

Hooray! Let's save it.

; reboot and re-run the trace so we get
; the original code (in case it's self-
; modifying)
*C500G
...
]BRUN TRACE3
...reboots slot 6...
<beep>
*C500G
...
]BSAVE OBJ.1700-1FFF,A$1700,L$900

Whew.

Continuing from $0482...

]BLOAD BOOT1
]CALL -151

*2482L

; another multi-sector read
; Y = first logical sector ($09)
; X = last logical sector ($0B)
; A = start address high byte ($40)
2482-   A9 40       LDA   #$40
2484-   A0 09       LDY   #$09
2486-   A2 0B       LDX   #$0B
2488-   20 15 04    JSR   $0415

So, three sectors into $4000..$42FF.

; call the code we just read
248B-   20 00 42    JSR   $4200

*9600<C600.C6FFM

; set up callback (same as previous
; trace)
96F8-   A9 97       LDA   #$97
96FA-   8D 18 08    STA   $0818
96FD-   A9 04       LDA   #$04
96FF-   8D 54 08    STA   $0854

; start the boot
9702-   4C 01 08    JMP   $0801

; break to monitor after reading those
; three sectors into $4000..$42FF and
; before calling $4200
9705-   A9 4C       LDA   #$4C
9707-   8D 8B 04    STA   $048B
970A-   A9 59       LDA   #$59
970C-   8D 8C 04    STA   $048C
970F-   A9 FF       LDA   #$FF
9711-   8D 8D 04    STA   $048D

; continue the boot
9714-   4C 73 04    JMP   $0473

*BSAVE TRACE4,A$9600,L$117
*9600G
...reboots slot 6...
<beep>

*4200L

4200-   8D 81 C0    STA   $C081

; check machine ID
4203-   AD B3 FB    LDA   $FBB3
4206-   C9 06       CMP   #$06
4208-   D0 03       BNE   $420D

; Apple //e or later
420A-   A9 03       LDA   #$03
420C-   60          RTS

; Apple ][+ or earlier -- check for 64K
420D-   8D 83 C0    STA   $C083
4210-   8D 83 C0    STA   $C083
4213-   8D 00 E0    STA   $E000
4216-   CD 00 E0    CMP   $E000
4219-   D0 19       BNE   $4234
421B-   2A          ROL
421C-   8D 00 E0    STA   $E000
421F-   CD 00 E0    CMP   $E000
4222-   D0 10       BNE   $4234
4224-   8D 81 C0    STA   $C081
4227-   A9 4C       LDA   #$4C
4229-   CD 00 E0    CMP   $E000
422C-   F0 03       BEQ   $4231
422E-   D0 1C       BNE   $424C
4230-   60          RTS

; Apple ][+ with 64K
4231-   A9 02       LDA   #$02
4233-   60          RTS

; check for Applesoft in ROM
4234-   8D 81 C0    STA   $C081
4237-   A9 4C       LDA   #$4C
4239-   CD 00 E0    CMP   $E000
423C-   F0 0B       BEQ   $4249
423E-   8D 80 C0    STA   $C080
4241-   CD 00 E0    CMP   $E000
4244-   D0 06       BNE   $424C

; Applesoft in ROM
4246-   A9 01       LDA   #$01
4248-   60          RTS

; Applesoft in RAM
4249-   A9 00       LDA   #$00
424B-   60          RTS

; display error message ("REQUIRES
; APPLESOFT") and hang
424C-   8D 81 C0    STA   $C081
424F-   A6 2B       LDX   $2B
4251-   BD 88 C0    LDA   $C088,X
4254-   20 58 FC    JSR   $FC58
4257-   8D 51 C0    STA   $C051
425A-   BA          TSX
425B-   BD 00 01    LDA   $0100,X
425E-   85 07       STA   $07
4260-   A9 7E       LDA   #$7E
4262-   85 06       STA   $06
4264-   2C 54 C0    BIT   $C054
4267-   2C 52 C0    BIT   $C052
426A-   2C 51 C0    BIT   $C051
426D-   A9 0C       LDA   #$0C
426F-   20 5B FB    JSR   $FB5B
4272-   A0 00       LDY   #$00
4274-   B1 06       LDA   ($06),Y
4276-   F0 FE       BEQ   $4276
4278-   20 F0 FD    JSR   $FDF0
427B-   C8          INY
427C-   D0 F6       BNE   $4274

OK, that's all perfectly legitimate
compatibility checking code. Nothing
protection-related. Moving on.

                   ~

               Chapter 6
         Spiraling, Spiraling,
       Spiraling Towards Freedom


Continuing from $048E...

*248EL

; read 4 sectors into $9D00+
248E-   A9 9D       LDA   #$9D
2490-   A0 0C       LDY   #$0C
2492-   A2 0F       LDX   #$0F
2494-   20 15 04    JSR   $0415

OK, I can capture that too.

*9600<C600.C6FFM

; set up callback #1 (same as previous
; trace)
96F8-   A9 97       LDA   #$97
96FA-   8D 18 08    STA   $0818
96FD-   A9 04       LDA   #$04
96FF-   8D 54 08    STA   $0854

; start the boot
9702-   4C 01 08    JMP   $0801

; (callback #1 is here)
; set up callback #2 after reading
; into $9D00+
9705-   A9 4C       LDA   #$4C
9707-   8D 97 04    STA   $0497
970A-   A9 17       LDA   #$17
970C-   8D 98 04    STA   $0498
970F-   A9 97       LDA   #$97
9711-   8D 99 04    STA   $0499

; continue the boot
9714-   4C 73 04    JMP   $0473

; (callback #2 is here)
; copy the 4 sectors to lower memory so
; they'll survive a reboot (otherwise
; they would be overwritten by DOS when
; I reboot to my work disk)
9717-   A2 04       LDX   #$04
9719-   A0 00       LDY   #$00
971B-   B9 00 9D    LDA   $9D00,Y
971E-   99 00 2D    STA   $2D00,Y
9721-   C8          INY
9722-   D0 F7       BNE   $971B
9724-   EE 1D 97    INC   $971D
9727-   EE 20 97    INC   $9720
972A-   CA          DEX
972B-   D0 EE       BNE   $971B

; turn off drive motor and reboot
972D-   AD E8 C0    LDA   $C0E8
9730-   4C 00 C5    JMP   $C500

*BSAVE TRACE5,A$9600,L$133
*9600G
...reboots slot 6...
...reboots slot 5...

]BSAVE OBJ.9D00-A0FF,A$2D00,L$400
]BLOAD BOOT1
]CALL -151

*2497L

2497-   20 DA 04    JSR   $04DA

*24DAL

; advance drive head to next track
24DA-   20 33 04    JSR   $0433

[now on track 2]

; zero page fiddling (see below)
24DD-   A9 00       LDA   #$00
24DF-   85 41       STA   $41
24E1-   38          SEC
24E2-   66 4A       ROR   $4A

; another multi-sector read
; Y = first logical sector ($01)
; X = last logical sector ($05)
; A = start address high byte ($08)
24E4-   A9 08       LDA   #$08
24E6-   A0 01       LDY   #$01
24E8-   A2 05       LDX   #$05
24EA-   20 15 04    JSR   $0415

; move the drive head one phase only,
; to the next HALF track
24ED-   20 36 04    JSR   $0436

[now on track 2.5]

; read more sectors ($06..$0A)
24F0-   A2 0A       LDX   #$0A
24F2-   20 15 04    JSR   $0415

; advance another half track
24F5-   20 36 04    JSR   $0436

[now on track 3]

; read more sectors ($0B..$0F)
24F8-   A2 0F       LDX   #$0F
24FA-   20 15 04    JSR   $0415

; fiddle with $4A again
24FD-   46 4A       LSR   $4A
24FF-   60          RTS

So here's the deal with $4A: we
initialized it at $0473 by a blind LSR,
which clears the high bit. This tells
the multi-sector read routine at $0415
to use logical sectors. Then we set the
high bit at $04A6 with SEC + ROR,
indicating we want $0415 to read
physical sectors. Then we read a few
sectors from track 2, a few from track
2.5, and a few from track 3. Then we
reset $4A with another LSR, and we're
back to using logical sectors.

This explains why my EDD bit copy
failed. This disk is storing data on
half tracks. Worse, it's storing data
on *adjacent* half tracks -- a few from
track 2, a few from track 2.5, and a
few from track 3. Due to limitations of
the Disk II drive mechanism, that would
be virtually impossible for a generic
bit copier to reproduce on a blank
floppy disk.

Every part of this code is brilliant,
AND it fits in a single sector on the
text page, AND it's flexible enough to
read from virtually uncopyable disks.

*9600<C600.C6FFM

; set up callback #1
96F8-   A9 97       LDA   #$97
96FA-   8D 18 08    STA   $0818
96FD-   A9 04       LDA   #$04
96FF-   8D 54 08    STA   $0854

; start the boot
9702-   4C 01 08    JMP   $0801

; (callback #1 is here)
; set up callback #2
9705-   A9 4C       LDA   #$4C
9707-   8D 9A 04    STA   $049A
970A-   A9 17       LDA   #$17
970C-   8D 9B 04    STA   $049B
970F-   A9 97       LDA   #$97
9711-   8D 9C 04    STA   $049C

; continue the boot
9714-   4C 73 04    JMP   $0473

; (callback #2 is here)
; relocate the code that we read from
; the "spiral" between tracks 2 and 3
9717-   A2 0F       LDX   #$0F
9719-   A0 00       LDY   #$00
971B-   B9 00 08    LDA   $0800,Y
971E-   99 00 28    STA   $2800,Y
9721-   C8          INY
9722-   D0 F7       BNE   $971B
9724-   EE 1D 97    INC   $971D
9727-   EE 20 97    INC   $9720
972A-   CA          DEX
972B-   D0 EE       BNE   $971B
972D-   AD E8 C0    LDA   $C0E8
9730-   4C 00 C5    JMP   $C500

*BSAVE TRACE6,A$9600,L$133
*9600G
...reboots slot 6...
...reboots slot 5...

]BSAVE OBJ.0800-16FF,A$2800,L$F00

                   ~

               Chapter 7
         Into The Home Stretch


Continuing from $049A...

]BLOAD BOOT1
]CALL -151

*249AL

; advance drive head
249A-   20 33 04    JSR   $0433

[now on track 4]

; read 9 sectors ($00..$08) into $1700
; (note: overwrites the first read we
; did earlier, but I guess we're done
; with that now that we've displayed
; the title screen)
249D-   A9 17       LDA   #$17
249F-   A2 08       LDX   #$08
24A1-   20 13 04    JSR   $0413

; read 7 sectors ($09..$0F) into $4200
24A4-   A9 42       LDA   #$42
24A6-   A2 0F       LDX   #$0F
24A8-   20 15 04    JSR   $0415

24AB-   A9 A0       LDA   #$A0
24AD-   20 0B 04    JSR   $040B

This is very interesting. $0405 looks
like this:

*2405L

2405-   20 0E 04    JSR   $040E
2408-   20 0E 04    JSR   $040E
240B-   20 0E 04    JSR   $040E
240E-   20 33 04    JSR   $0433
2411-   A2 0F       LDX   #$0F
2413-   A0 00       LDY   #$00
2415-   85 27       STA   $27
.
. multi-sector read routine (see above)
.

$0411 sets X and Y to read an entire
track (sector $00 through $0F). Before
that, $040E advances to the next whole
track. And before *that*, we have three
identical JSRs to $040E, each of which
falls through to the next, and
eventually to $040E again.

Thus, calling $040E will advance one
whole track and read one whole track.
Calling $040B will do that twice,
reading each track into consecutive
memory (because the multi-sector read
routine ends with next page in memory
in the accumulator, so you can chain
them and just fill up memory without
having to reset the starting page).

Calling $0408 will do it three times,
and calling $0405 will do it four times
(again, into consecutive memory). So
these two lines of code...

24AB-   A9 A0       LDA   #$A0
24AD-   20 0B 04    JSR   $040B

...will advance the drive head (to
track 5, at this point), read the
entire track into $A000..$AFFF, advance
the drive head again to track 6, read
that entire track into $B000..$BFFF,
and return gracefully.

Such compact. Many elegance. Wow.

*9600<C600.C6FFM

; set up callback #1
96F8-   A9 97       LDA   #$97
96FA-   8D 18 08    STA   $0818
96FD-   A9 04       LDA   #$04
96FF-   8D 54 08    STA   $0854

; start the boot
9702-   4C 01 08    JMP   $0801

; (callback #1 is here)
; set up callback #2
9705-   A9 4C       LDA   #$4C
9707-   8D B0 04    STA   $04B0
970A-   A9 17       LDA   #$17
970C-   8D B1 04    STA   $04B1
970F-   A9 97       LDA   #$97
9711-   8D B2 04    STA   $04B2

; continue the boot
9714-   4C 73 04    JMP   $0473

; (callback #2 is here)
; relocate the code we read into $A000+
9717-   A2 20       LDX   #$20
9719-   A0 00       LDY   #$00
971B-   B9 00 A0    LDA   $A000,Y
971E-   99 00 20    STA   $2000,Y
9721-   C8          INY
9722-   D0 F7       BNE   $971B
9724-   EE 1D 97    INC   $971D
9727-   EE 20 97    INC   $9720
972A-   CA          DEX
972B-   D0 EE       BNE   $971B
972D-   AD E8 C0    LDA   $C0E8
9730-   4C 00 C5    JMP   $C500

*BSAVE TRACE7,A$9600,L$133
*9600G
...reboots slot 6...
...reboots slot 5...

Let's see, there are three chunks of
code (or data, or whatever) that we
captured. Two are still in their
original location, and one we moved to
hi-res page 1.

]BSAVE OBJ.1700-1FFF AGAIN,A$1700,L$900
]BSAVE OBJ.4200-48FF,A$4200,L$700
]BSAVE OBJ.A000-BFFF,A$2000,L$2000

Whew.

                   ~

               Chapter 8
       In Which We See The Light
       At The End Of The Tunnel,
       And It's A DOS-shaped RWTS
       Which Is A Weird Thing To
       See In A Tunnel, Honestly


Continuing from $04B0...

]BLOAD BOOT1
]CALL -151

*24B0L

; move some stuff around
24B0-   A5 3F       LDA   $3F
24B2-   8D 56 BC    STA   $BC56
24B5-   A0 5F       LDY   #$5F
24B7-   B9 00 48    LDA   $4800,Y
24BA-   99 00 03    STA   $0300,Y
24BD-   88          DEY
24BE-   10 F7       BPL   $24B7

; set up DOS globals (tracking where
; the drive head is)
24C0-   A6 2B       LDX   $2B
24C2-   8E E9 B7    STX   $B7E9
24C5-   20 8E BE    JSR   $BE8E
24C8-   A5 FC       LDA   $FC
24CA-   99 78 04    STA   $0478,Y
24CD-   4A          LSR
24CE-   8D 78 04    STA   $0478

; push $B7/$3A to the stack
24D1-   A9 B7       LDA   #$B7
24D3-   48          PHA
24D4-   A9 3A       LDA   #$3A
24D6-   48          PHA

; an exit via HOME
24D7-   4C 58 FC    JMP   $FC58

Since we just pushed an address to the
stack, we'll "return" to $B73A + 1 =
$B73B. Hey, that's still in memory!

*373BL

373B-   A2 FF       LDX   #$FF
373D-   9A          TXS
373E-   8E EB B7    STX   $B7EB
3741-   20 93 FE    JSR   $FE93
3744-   20 89 FE    JSR   $FE89
3747-   4C 84 9D    JMP   $9D84

Well that looks suspiciously like a
late-stage DOS boot. In fact, we now
have a lot of clues that -- despite the
craziness -- we have in fact loaded a
full copy of DOS into the usual place
at the top of writeable main memory
($9D00..$BFFF).

*3800L
.
. appears to be a DOS-shaped RWTS
.

*3944L

; standard routine to find the address
; prologue ("D5 AA 96")
3944-   A0 FC       LDY   #$FC
3946-   84 26       STY   $26
3948-   C8          INY
3949-   D0 04       BNE   $394F
394B-   E6 26       INC   $26
394D-   F0 F3       BEQ   $3942
394F-   BD 8C C0    LDA   $C08C,X
3952-   10 FB       BPL   $394F
3954-   C9 D5       CMP   #$D5
3956-   D0 F0       BNE   $3948
3958-   EA          NOP
3959-   BD 8C C0    LDA   $C08C,X
395C-   10 FB       BPL   $3959
395E-   C9 AA       CMP   #$AA
3960-   D0 F2       BNE   $3954
3962-   A0 03       LDY   #$03
3964-   BD 8C C0    LDA   $C08C,X
3967-   10 FB       BPL   $3964
3969-   C9 96       CMP   #$96
396B-   D0 E7       BNE   $3954
.
.
.
; non-standard epilogue bytes (FF FF)
398B-   BD 8C C0    LDA   $C08C,X
398E-   10 FB       BPL   $398B
3990-   C9 FF       CMP   #$FF
3992-   D0 AE       BNE   $3942
3994-   EA          NOP
3995-   BD 8C C0    LDA   $C08C,X
3998-   10 FB       BPL   $3995
399A-   C9 FF       CMP   #$FF
399C-   D0 A4       BNE   $3942
399E-   18          CLC
399F-   60          RTS

Confirmed: after the bootloader exits,
we have a full DOS 3.3 in memory. Spot
checking the RWTS, it's perfectly
normal except it expects "FF FF FF"
epilogue bytes. Which, by the way, is
just the sort of RWTS that could read
tracks $07-$18.

                   ~

               Chapter 9
       Do You Know Demuffin Man,
       Demuffin Man, Demuffin Man?
       Do You Know Demuffin Man
       Who Lives On Floppy Lane?


Advanced Demuffin (written in 1983 by
The Stack, updated in 2014 by yours
truly) is a wonderful automation tool
that uses a copy protected disk against
itself. It literally calls the original
RWTS to read the original disk, then
using a standard RWTS to write out the
sector data to another disk, but in a
standard format. You just supply the
original disk's RWTS and choose which
tracks and sectors to convert, and it
does the rest.

As it happens, I already have the
original RWTS in memory (though I'll
move it into place later).

[S5,D1=my work disk (still)]
[S6,D1=original disk (still)]
[S6,D2=blank disk]

]BRUN ADVANCED DEMUFFIN 1.5

["X" to exit to monitor]

*B800<3800.3FFFM ; move RWTS into place

*800G            ; resume Advanced DM

["C" to convert disk]

["Y" to change default values]

                 --v--

ADVANCED DEMUFFIN 1.5    (C) 1983, 2014
ORIGINAL BY THE STACK    UPDATES BY 4AM
=======================================


INPUT ALL VALUES IN HEX


SECTORS PER TRACK? (13/16) 16

START TRACK: $07        <-- change this
START SECTOR: $00
END TRACK: $18          <-- change this
END SECTOR: $0F

INCREMENT: 1

MAX # OF RETRIES: 0

COPY FROM DRIVE 1
TO DRIVE: 2
=======================================
16SC $07,$00-$18,$0F BY1.0 S6,D1->S6,D2

                 --^--

And here we go...

                 --v--

ADVANCED DEMUFFIN 1.5    (C) 1983, 2014
ORIGINAL BY THE STACK    UPDATES BY 4AM
=======PRESS ANY KEY TO CONTINUE=======
TRK:       ..................
+.5:
    0123456789ABCDEF0123456789ABCDEF012
SC0:       ..................
SC1:       ..................
SC2:       ..................
SC3:       ..................
SC4:       ..................
SC5:       ..................
SC6:       ..................
SC7:       ..................
SC8:       ..................
SC9:       ..................
SCA:       ..................
SCB:       ..................
SCC:       ..................
SCD:       ..................
SCE:       ..................
SCF:       ..................
=======================================
16SC $07,$00-$18,$0F BY1.0 S6,D1->S6,D2

                 --^--

This is the power and the genius of
Advanced Demuffin. Every disk must be
able to read itself. So, let it read
itself, then capture the data and write
it out in a standard format.

I also copied track 0 the same way, by
using Advanced Demuffin to read the
original disk and write out the track
to write it out to the disk in S6,D2.
Now I have all tracks converted that
can be easily converted.

                   ~

              Chapter 10
     In Which Everything Is Simple
    If You Look At It The Right Way


Now I get to write everything that I
captured from the corrupted tracks back
to tracks $01-$06.

Here is the original disk layout:

track | sectors | address
------+---------+-------------
  1   | $00-$08 | $1700-$1FFF
  1   | $09-$0B | $4000-$42FF
  1   | $0C-$0F | $9D00-$A0FF
  2   | $01-$05 | $0800-$0CFF
 2.5  | $06-$0A | $0D00-$11FF
  3   | $0B-$0F | $1200-$16FF
  4   | $00-$08 | $1700-$1FFF
  4   | $09-$0F | $4200-$48FF
  5   | $00-$0F | $A000-$AFFF
  6   | $00-$0F | $B000-$BFFF

And here is the new layout:

track          | sectors | address
---------------+---------+-------------
1 \            | $00-$08 | $1700-$1FFF
1  } unchanged | $09-$0B | $4000-$42FF
1 /            | $0C-$0F | $9D00-$A0FF
2 (was 2/2.5/3)| $00-$0E | $0800-$16FF
3 (was 4)      | $00-$08 | $1700-$1FFF
3 (was 4)      | $09-$0F | $4200-$48FF
4 (was 5)      | $00-$0F | $A000-$AFFF
5 (was 6)      | $00-$0F | $B000-$BFFF
6              | unused

Of course, all the tracks will now have
normal address fields (no more lying
about the track numbers). Also no half
tracks and no spirals. Just, you know,
sectors on a disk. All the spiral track
stuff collapses into a single track, so
track $06 is completely unused.

[S6,D1=demuffin'd copy (T00, T07-T18)]
[S5,D1=my work disk]

]PR#5
...
]CALL -151

*300L

; page count (decremented)
0300-   A9 50       LDA   #$50
0302-   85 FF       STA   $FF

; logical sector (incremented)
0304-   A9 00       LDA   #$00
0306-   85 FE       STA   $FE

; call RWTS to write sector
0308-   A9 03       LDA   #$03
030A-   A0 88       LDY   #$88
030C-   20 D9 03    JSR   $03D9

; increment logical sector, wrap around
; from $0F to $00 and increment track
030F-   E6 FE       INC   $FE
0311-   A4 FE       LDY   $FE
0313-   C0 10       CPY   #$10
0315-   D0 07       BNE   $031E
0317-   A0 00       LDY   #$00
0319-   84 FE       STY   $FE
031B-   EE 8C 03    INC   $038C

; Convert to the interleave order that
; this disk expects
031E-   B9 40 03    LDA   $0340,Y
0321-   8D 8D 03    STA   $038D

; increment page to write
0324-   EE 91 03    INC   $0391
0327-   C6 FF       DEC   $FF

; loop until done with all pages
0329-   D0 DD       BNE   $0308
032B-   60          RTS

; sector interleave table
*340.34F

0340- 00 06 05 04 03 02 01 0F
0348- 0E 0D 0C 0B 0A 09 08 07

; RWTS parameter table, pre-initialized
; with slot 6, drive 1, track $01,
; sector $00, address $1000, and RWTS
; write command ($02)
*388.397

0388- 01 60 01 00 01 00 FB F7
0390- 00 10 00 00 02 00 00 60

*BSAVE MAKE,A$300,L$98

; clear memory
*800:00 N 801<800.7FFEM

; load the chunks in order, starting at
; $1000, so we can write them out to
; tracks 1-5 in one shot
*BLOAD OBJ.1700-1FFF,A$1000
*BLOAD OBJ.4000-42FF,A$1900
*BLOAD OBJ.9D00-A0FF,A$1C00
*BLOAD OBJ.0800-16FF,A$2000
*BLOAD OBJ.1700-1FFF AGAIN,A$3000
*BLOAD OBJ.4200-48FF,A$3900
*BLOAD OBJ.A000-BFFF,A$4000

; patch the RWTS to read standard disks
; (using the standard epilogue instead
; of FF FF)
*5935:DE
*593F:AA
*5991:DE
*599A:AA

; patch the RWTS to ignore the disk
; volume number in the address field
; (the original disk used disk volume
; V001 for tracks $07-$18, but our
; cracked copy uses the standard V254)
*5E12:A9 00

*300G
...write write write...

Now I need to modify the bootloader at
$0473 slightly.

  1. Modify the routine at $0433 (that
     advances the drive head) so it
     updates zero page $41 with the
     current track.

  2. Modify the routine at $04DA so it
     reads $0800..$16FF from track 2.
     No tricks, no traps, no spirals.

The sector read routine at $C65C
compares the track listed in the
address field to zero page $41 and
loops forever until it matches. $C600
initializes $41 to 0, and the original
disk never updates $41, but everything
works because the address fields are
corrupted and all claim to be track 0.

So part of it will be simpler, because
we'll no longer be spiraling between
tracks. But part of it will actually
be more complicated because the address
fields are no longer corrupted and we
need to track the track number.

First, a patch at $04DA to load track 2
directly into $0800. We can reuse the
entry point at $040E to read the entire
track. This will read one extra sector
into $1700, but it doesn't matter
because we'll overwrite that later when
we read $1700..$1FFF from track 4.

T00,S07,$DA -> A9 08 4C 0E 04

which looks like this (using Disk
Fixer's built-in disassembler):

                 --v--

----------- DISASSEMBLY MODE -----------
00DA:A9 08          LDA   #$08
00DC:4C 0E 04       JMP   $040E

                 --^--

Second, a new routine at $04DF (now
unused space) to increment the track
number in zero page.

T00,S07,$DF -> E6 41 4C 36 04

                 --v--

00DF:E6 41          INC   $41
00E1:4C 36 04       JMP   $0436

                 --^--

Third, the patch at $0433 to call the
new routine at $04DF so we increment
the track number in zero page before
advancing the drive arm.

T00,S07,$34: 36 -> DF

                 --v--

0033:20 DF 04       JSR   $04DF

                 --^--

]PR#6
...works, and it is glorious...

Quod erat liberandum.

---------------------------------------
A 4am crack                     No. 894
------------------EOF------------------
